Code
# Importer les packages
using CSV
using DataFrames
using Dates
using Statistics
using PlotsDans ce rapport, nous détaillons notre travail qui avait pour objectif de lisser des données ainsi que de les agréger en blocs de temps d’une durée d’une heure et quart.
Notre jeu de données provient de capteurs dans un lac en région parisienne qui mesure la concentration en chlorophylle en microgrammes par litre (µg/L) ainsi que la concentration en cyanobactéries (µg/L). Ces dernières sont des micro-organismes photosynthétiques qui peuvent proliférer dans les eaux douces et former des blooms nuisibles pour l’environnement et la santé humaine. Il est important de préciser que les cyanobactéries sont des bactéries photosynthétiques qui possèdent de la chlorophylle.
L’échantillonnage initial pour ces mesures est de 15 minutes, mais à cette granularité l’évolution des concentrations est quasiment imperceptible. De plus, cela pourrait fausser les analyses statistiques en introduisant une forte autocorrélation entre les observations successives. En regroupant les données en blocs de 1h15, nous nous attendons à obtenir une vision plus claire des tendances et des variations des concentrations de chlorophylle et de cyanobactéries dans le lac.
Le long de ce travail, nous utiliserons le langage de programmation Julia (mettre la ref), qui permet les applications de calcul numérique et la visualisation graphique. Tout nos codes sont disponibles en opensource sur notre page Github via ce lien (mettre lien).
Tout d’abord, afin de comprendre un peu mieux notre jeu de données, nous avons commencé par effectuer des visualisations. Pour cela, nous commençons par charger les bibliothèques nécessaires à la mnipulation des données, à la gestion des dates et à la visualisation.
# Importer les packages
using CSV
using DataFrames
using Dates
using Statistics
using PlotsLes données sont ensuite importées dans un tableau (dataframe), ce qui va nous permettre une manipulation structurée de nos données.
# Importer les données
data = CSV.read("data_chl_cyano.csv", DataFrame)
first(data, 5)| Row | date | Chl | Cyano |
|---|---|---|---|
| String31 | Float64? | Float64? | |
| 1 | 15/10/2022 00:01 | 5.13 | 3.87 |
| 2 | 15/10/2022 00:16 | 4.78 | 3.51 |
| 3 | 15/10/2022 00:31 | 5.0 | 3.09 |
| 4 | 15/10/2022 00:46 | 4.55 | 3.85 |
| 5 | 15/10/2022 01:01 | 5.01 | 3.18 |
Suite à l’importation de nos données sous forme d’un dataframe Julia et la visualisation des cinq premières lignes, nous observons trois variables principales :
date).Chl).Cyano).De plus, nous remarquons que la colonne de date est de type String31, e qui empêche toute manipulation temporelle. Ainsi, nous convertissons cette colonne en format DateTime afin de permettre des opérations temporelles telles que l’agrégation ou la représentation graphique en fonction du temps.
# Préparation des dates et des horaires
data.date = tryparse.(DateTime, data.date, dateformat"dd/mm/yyyy HH:MM")
println("Premières lignes :")
display(first(data, 5))
println("\nDernières lignes :")
display(last(data, 5))Premières lignes :
| Row | date | Chl | Cyano |
|---|---|---|---|
| DateTime | Float64? | Float64? | |
| 1 | 2022-10-15T00:01:00 | 5.13 | 3.87 |
| 2 | 2022-10-15T00:16:00 | 4.78 | 3.51 |
| 3 | 2022-10-15T00:31:00 | 5.0 | 3.09 |
| 4 | 2022-10-15T00:46:00 | 4.55 | 3.85 |
| 5 | 2022-10-15T01:01:00 | 5.01 | 3.18 |
Dernières lignes :
| Row | date | Chl | Cyano |
|---|---|---|---|
| DateTime | Float64? | Float64? | |
| 1 | 2024-08-31T16:16:00 | 65.75 | 2.15 |
| 2 | 2024-08-31T16:31:00 | 41.02 | 2.36 |
| 3 | 2024-08-31T16:46:00 | 44.76 | 2.29 |
| 4 | 2024-08-31T17:01:00 | 39.5 | 2.01 |
| 5 | 2024-08-31T17:16:00 | 42.19 | 2.67 |
Nous avons commencé par utiliser la fonction DateTime mais celle-ci ne fonctionnait pas car certaines lignes étaient mals formattées. Nous avons donc essayé une méthode plus robuste (fonction tryparse) qui transforme les dates incalides en missing plutôt que faire planter le code.
Nous cherchons maintenant à observer l’évolution temporelle de la concentration en chlorophylle à la résolution initiale de 15 minutes (sans transformation ni lissage). Puis celle de la concentration en cyanobactéries en fonction du temps.
# Visualisation graphique de la chlorophylle
plot(
data.date,
data.Chl,
xlabel = "Temps (intervalles de 15 minutes)",
ylabel = "Chlorophylles (µg/L)",
title = "Évolution brute de la concentration en chlorophylles",
legend = false,
color = "#6495ED"
)# Visualisation graphique de la cyanobactérie
plot(
data.date,
data.Cyano,
xlabel = "Temps (intervalles de 15 minutes)",
ylabel = "Cyanobactéries (µg/L)",
title = "Évolution brute de la concentration en cyanobactéries",
legend = false,
color = "#6495ED"
)Nos figures représentent l’évolution brute de la concentration en chlorophylle et en cyanobactéries en fonction du temps (du 15 octobre 2022 au 31 août 2024). Elles mettent en évidence une forte variabilité temporelle, c’est-à-dire que, pour les deux variables, les valeurs sont généralement faubles mais ponctuées de pics importants, parfois très marqués, apparaissant de manière irrégulières au cours du temps.
Ces pics pourraient refléter des évènements biologiques réels mais pourraient également être dûes à des bruits de mesures des capteurs, à des variations lcoales du courant dans le lac ou encore à des artéfacts de mesure. Cette granularité de mesurfe trop fine (15 minutes) empêche de distinguer clairement les tendances biologiques réelles des variations aléatoires.
Par ailleurs, nous constatons que les profils temporels de la chlorophylle et des cyanobactéries présentent des similitudes, ce qui est cohérent du point de vue biologique puisque les cyanobactéries contiennent de la chlorophylle. Néanmoins, les pics de cyanobactérieues sont généralement moins élevés et moins fréquents que ceux de la chlorophylle totale.
Face à ces observations, il apparaît important de procéder à un lissage et une agrégation de données pour plusieurs raisons. Tout d’abord, pour réduire le bruit de mesure. En effet, les capteurs peuvent générer des fluctuations artificielles dûes à des soucis techniques. Le lissage pourrait permettre d’atténuer ces variations parasites qui ne reflètent pas la dynamique réelle des concentrations. Ensuite, pour mettre en évidence les tendances biolgiques. En effet, les processus biologiques tels que la croissance des cyanmybactéries ou la phyotosynthèse se déroulent sur des échelles de temps plus longues que 15 minutes. En regroupant les données par blocs de 1h15, nous nous plaçons à une échelle temporelle plus cohérente avec ces phénomènes biologiques, ce qui facilitera l’identification des évolution écologiques. De plus, pour réduire l’autocorrélation. En effet, à l’échelle de 15 minutes, les mesures successives sont très fortement corrélées entre elles, ce qui est contre les hypothèses de nombreux tests statistiques, car cela peut conduite à surestimer la significativité des résultats. L’agrégation par blocs de 1h&5 permet de réduire cette dépendance temporelle et d’obtenir des observations plus indépendantes. Pour finir, pour facilier l’analyse. En effet, un jeu de données moins volumineux et moins bruité est plus facile à manipuler, à visualiser et à analyser. Cela permet également de réduire les coûts de calcul pour les analyses ultérieures.
Le choxi spécifique d’une fenêtre de 1h15 semble représenter un bon compromis entre la conservation d’une résolution temporelle suffisante pour observer les dynamiques journalières et l’obtention d’une réduction signification du bruit et du volume de données.
Nous allons maintenant procéder à l’agrégation des données par blocs de 1h15 (75 minutes). Pour cela nous devons : - Créer une nouvelle colonne qui attribue à chaque mesure un bloc temporel. - Regrouper les mesures appartenant au même bloc. - Calculer une statistique agrégée (moyenne ou médiane) pour chaque bloc.
La première étape consiste donc à procéder à l’agrégation des données. Comme nos mesures sont effectuées toutes les 15 minutes, chaque bloc contiendra exactement 5 mesures consécutives (5 x 15 min = 75 min).
# Créer les numéros de blocs : 1,1,1,1,1, 2,2,2,2,2, 3,3,3,3,3, etc.
n_lignes = nrow(data)
data.bloc = repeat(1:ceil(Int, n_lignes/5), inner=5)[1:n_lignes]
# Afficher les premières lignes pour vérifier
first(data, 15)| Row | date | Chl | Cyano | bloc |
|---|---|---|---|---|
| DateTime | Float64? | Float64? | Int64 | |
| 1 | 2022-10-15T00:01:00 | 5.13 | 3.87 | 1 |
| 2 | 2022-10-15T00:16:00 | 4.78 | 3.51 | 1 |
| 3 | 2022-10-15T00:31:00 | 5.0 | 3.09 | 1 |
| 4 | 2022-10-15T00:46:00 | 4.55 | 3.85 | 1 |
| 5 | 2022-10-15T01:01:00 | 5.01 | 3.18 | 1 |
| 6 | 2022-10-15T01:16:00 | 4.86 | 3.29 | 2 |
| 7 | 2022-10-15T01:31:00 | 5.03 | 3.33 | 2 |
| 8 | 2022-10-15T01:46:00 | 4.84 | 3.24 | 2 |
| 9 | 2022-10-15T02:01:00 | 4.77 | 2.94 | 2 |
| 10 | 2022-10-15T02:16:00 | 5.2 | 3.36 | 2 |
| 11 | 2022-10-15T02:31:00 | 4.62 | 3.07 | 3 |
| 12 | 2022-10-15T02:46:00 | 4.52 | 3.11 | 3 |
| 13 | 2022-10-15T03:01:00 | 4.36 | 3.15 | 3 |
| 14 | 2022-10-15T03:16:00 | 4.41 | 3.03 | 3 |
| 15 | 2022-10-15T03:31:00 | 5.45 | 3.07 | 3 |
Les dates confirment bien qu’il y a bien 15 minutes d’écart entre chaque mesure. Nous allons donc calculer la moyenen de la chlorophylle et des cyanobactéries pour chaque bloc de 1h15.
# Calculer les moyennes par bloc
data_agregee = combine(groupby(data, :bloc),
:date => first => :date_debut, # Date de début du bloc
:Chl => mean => :Chl_moyenne, # Moyenne de la chlorophylle
:Cyano => mean => :Cyano_moyenne # Moyenne des cyanobactéries
)
# Afficher les premières et dernières lignes
println("Premières lignes agrégées :")
display(first(data_agregee, 10))
println("\nDernières lignes agrégées :")
display(last(data_agregee, 10))Premières lignes agrégées :
| Row | bloc | date_debut | Chl_moyenne | Cyano_moyenne |
|---|---|---|---|---|
| Int64 | DateTime | Float64? | Float64? | |
| 1 | 1 | 2022-10-15T00:01:00 | 4.894 | 3.5 |
| 2 | 2 | 2022-10-15T01:16:00 | 4.94 | 3.232 |
| 3 | 3 | 2022-10-15T02:31:00 | 4.672 | 3.086 |
| 4 | 4 | 2022-10-15T03:46:00 | 4.428 | 2.986 |
| 5 | 5 | 2022-10-15T05:01:00 | 4.022 | 2.946 |
| 6 | 6 | 2022-10-15T06:16:00 | 4.126 | 2.824 |
| 7 | 7 | 2022-10-15T07:31:00 | 3.768 | 2.798 |
| 8 | 8 | 2022-10-15T08:46:00 | 4.028 | 2.852 |
| 9 | 9 | 2022-10-15T10:01:00 | 3.682 | 2.896 |
| 10 | 10 | 2022-10-15T11:16:00 | 3.738 | 2.968 |
Dernières lignes agrégées :
| Row | bloc | date_debut | Chl_moyenne | Cyano_moyenne |
|---|---|---|---|---|
| Int64 | DateTime | Float64? | Float64? | |
| 1 | 12293 | 2024-08-31T05:46:00 | 28.596 | 2.136 |
| 2 | 12294 | 2024-08-31T07:01:00 | 30.492 | 2.25 |
| 3 | 12295 | 2024-08-31T08:16:00 | 31.83 | 2.088 |
| 4 | 12296 | 2024-08-31T09:31:00 | 33.53 | 2.134 |
| 5 | 12297 | 2024-08-31T10:46:00 | 37.58 | 2.246 |
| 6 | 12298 | 2024-08-31T12:01:00 | 34.49 | 2.142 |
| 7 | 12299 | 2024-08-31T13:16:00 | 41.77 | 2.094 |
| 8 | 12300 | 2024-08-31T14:31:00 | 38.862 | 2.02 |
| 9 | 12301 | 2024-08-31T15:46:00 | 47.366 | 2.264 |
| 10 | 12302 | 2024-08-31T17:01:00 | 40.845 | 2.34 |
Nous remarquons, au-délà du fait que les agrégations ont bien fonctionnées, que la chlorophylle semble avoir fortement augmenté en fin de période (été 2024). En revanche, les syanobactéries restent relativements stables autour de 1-3 µg/L, même s’il semble y avoir une légère diminution.
Afin de vérifier si la lissage a bien fonctionné et si les tendances sont plus claires, nous allons visualiser nos nouvelles données.
# Graphique de la chlorophylle agrégée
plot(
data_agregee.date_debut,
data_agregee.Chl_moyenne,
xlabel = "Temps (intervalles de 1h15)",
ylabel = "Chlorophylles moyennes (µg/L)",
title = "Évolution de la concentration en chlorophylles",
legend = false,
linewidth = 2,
color = "#6495ED"
)# Graphique des cyanobactéries agrégées
plot(
data_agregee.date_debut,
data_agregee.Cyano_moyenne,
xlabel = "Temps (intervalles de (1h15)",
ylabel = "Cyanobactéries moyennes (µg/L)",
title = "Évolution de la concentration en cyanobactéries",
legend = false,
linewidth = 2,
color = "#6495ED"
)Il nous semble que les courbes ont bien été lissées. En effet, nous voyons que les concentrations maximales ont extrêmement dimunuées dans les deux cas (de 600 à 200 µg/L pour les chlorophylles et de 85 à 20 µg/L pour les cyanobactéries).
Pour mieux observer l’effet du lissage, nous superposons sur un même graphique les données brutes (mesures toutes les 15 minutes) et les données agrégées (blocs de 1h15).
# Comparaison pour la chlorophylle
plot(
data.date,
data.Chl,
label = "Données brutes (15 min)",
xlabel = "Temps",
ylabel = "Chlorophylle (µg/L)",
title = "Comparaison : données brutes vs agrégées (Chlorophylle)",
alpha = 0.4, # Transparence pour les données brutes
color = "#6495ED"
)
plot!(
data_agregee.date_debut,
data_agregee.Chl_moyenne,
label = "Données agrégées (1h15)",
linewidth = 2,
color = :darkred
)# Comparaison pour les cyanobactéries
plot(
data.date,
data.Cyano,
label = "Données brutes (15 min)",
xlabel = "Temps",
ylabel = "Cyanobactéries (µg/L)",
title = "Comparaison : données brutes vs agrégées (Cyanobactéries)",
alpha = 0.4, # Transparence pour les données brutes
color = "#6495ED"
)
plot!(
data_agregee.date_debut,
data_agregee.Cyano_moyenne,
label = "Données agrégées (1h15)",
linewidth = 2,
color = :darkred
)Ces graphiques mettent clairement en évidence l’effet du lissage par agrégation.
Pour la chlorophylle, les données brutes (en bleu) présentent une très forte variabilité avec des pics extrêmement marqués et de nombreuses fluctuations rapides. La courbe agrégée (en rouge foncé) suit la même tendance générale mais de manière beaucoup plus lisse et régulière. On observe que les pics importants sont conservés (début 2023, début 2024, etc.), mais leur amplitude est légèrement atténuée. Le lissage permet de distinguer plus clairement les périodes de forte concentration (blooms) des périodes calmes, sans être perturbé par le bruit de mesure.
Pour les cyanobactéries, le même phénomène s’observe avec la courbe bleue (données brutes) présentant des pics très abrupts, notamment un pic majeur au printemps 2023 atteignant près de 90 µg/L, et plusieurs autres pics en 2024. La courbe agrégée (en rouge foncé) atténue ces variations tout en conservant l’information sur les événements de prolifération. On remarque que les cyanobactéries sont globalement à des niveaux faibles (< 5 µg/L) la majorité du temps, avec des blooms ponctuels et saisonniers.